#region Copyright (c) 2003, newtelligence AG. All rights reserved.
/*
// Copyright (c) 2003, newtelligence AG. (http://www.newtelligence.com)
// Original BlogX Source Code: Copyright (c) 2003, Chris Anderson (http://simplegeek.com)
// All rights reserved.
//  
// Redistribution and use in source and binary forms, with or without modification, are permitted 
// provided that the following conditions are met: 
//  
// (1) Redistributions of source code must retain the above copyright notice, this list of 
// conditions and the following disclaimer. 
// (2) Redistributions in binary form must reproduce the above copyright notice, this list of 
// conditions and the following disclaimer in the documentation and/or other materials 
// provided with the distribution. 
// (3) Neither the name of the newtelligence AG nor the names of its contributors may be used 
// to endorse or promote products derived from this software without specific prior 
// written permission.
//      
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 
// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 
// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 
// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// -------------------------------------------------------------------------
//
// Original BlogX source code (c) 2003 by Chris Anderson (http://simplegeek.com)
// 
// newtelligence is a registered trademark of newtelligence Aktiengesellschaft.
// 
// For portions of this software, the some additional copyright notices may apply 
// which can either be found in the license.txt file included in the source distribution
// or following this notice. 
//
*/
#endregion

namespace newtelligence.DasBlog.Web.Services
{
	using System;
	using System.Configuration;
	using System.Collections;
	using System.Collections.Specialized;
	using System.ComponentModel;
	using System.Data;
	using System.Diagnostics;
	using System.Web;
	using System.Web.Services;
	using System.Web.Services.Protocols;
	using System.Web.Services.Description;
	using System.IO;
	using System.Xml;
	using System.Xml.Serialization;
	using newtelligence.DasBlog.Runtime;
	using newtelligence.DasBlog.Web.Services.Rss20;
	using newtelligence.DasBlog.Web.Services.Atom10;
	using newtelligence.DasBlog.Web.Core;
	using newtelligence.DasBlog.Util.Html;
    using System.ServiceModel.Web;
    using System.Net;
    using System.ServiceModel;
    using System.ServiceModel.Syndication;
    using System.Collections.Generic;
    using System.Web.Security;

	/// <summary>
	/// Summary description for DasBlogBrowsing.
	/// </summary>
	[WebService(Namespace="urn:schemas-newtelligence-com:dasblog:syndication-services")]
	public partial class WebWebServices 
	{
		protected bool inASMX = (HttpContext.Current!= null);

		[OperationContract, WebGet(UriTemplate="/rssurl")]
		public string GetRssUrl()
		{
			return Utils.GetRssUrl(siteConfig);
		}

        [OperationContract, WebGet(UriTemplate = "/commentsrssurl")]
		public string GetCommentsRssUrl() 
		{
			return Utils.GetCommentsRssUrl(siteConfig);
		}
		
        [OperationContract, WebGet(UriTemplate = "/comments?format=rss&guid={guid}")]
		public Rss20FeedFormatter GetEntryCommentsRss(string guid) 
		{
			CommentCollection _com;
            
            if ( guid == null )
                _com = dataService.GetAllComments();
            else
                _com = dataService.GetPublicCommentsFor(guid);
			_com = ReorderComments(_com);
			return new Rss20FeedFormatter(GetCommentsFeedCore(_com,guid));
		}

        [OperationContract, WebGet(UriTemplate = "/comments?format=atom&guid={guid}")]
		public Atom10FeedFormatter GetEntryCommentsAtom(string guid) 
		{
            CommentCollection _com;

            if (guid == null)
                _com = dataService.GetAllComments();
            else
                _com = dataService.GetPublicCommentsFor(guid);

			_com = ReorderComments(_com);
			return new Atom10FeedFormatter(GetCommentsFeedCore(_com,guid));
		}

        [OperationContract, WebGet(UriTemplate = "/feed?format=rss&category={category}&maxDays={maxDayCount}&maxEntries={maxEntryCount}")]
		public Rss20FeedFormatter GetRss(string category, string maxDayCount, string maxEntryCount)
		{
            int maxDays = siteConfig.RssDayCount;
            int maxEntries = siteConfig.RssMainEntryCount;
            if (maxDayCount != null) maxDays = int.Parse(maxDayCount);
            if (maxEntryCount != null) maxEntries = int.Parse(maxEntryCount);
			return new Rss20FeedFormatter(GetFeedCore(category, maxDays, maxEntries));
		}

        [OperationContract, WebGet(UriTemplate = "/feed?format=atom&category={category}&maxDays={maxDayCount}&maxEntries={maxEntryCount}")]
        public Atom10FeedFormatter GetAtom(string category, string maxDayCount, string maxEntryCount)
        {
            int maxDays = siteConfig.RssDayCount;
            int maxEntries = siteConfig.RssMainEntryCount;
            if (maxDayCount != null) maxDays = int.Parse(maxDayCount);
            if (maxEntryCount != null) maxEntries = int.Parse(maxEntryCount);
            return new Atom10FeedFormatter(GetFeedCore(category, maxDays, maxEntries));
        }

		private bool RedirectToFeedBurnerIfNeeded(string category)
		{
			if( inASMX )
			{
				//If we are using FeedBurner, only allow them to get our feed...
				if(siteConfig.FeedBurnerName != null && siteConfig.FeedBurnerName.Length > 0)
				{
					HttpContext current = HttpContext.Current;
					string userAgent = current.Request.UserAgent;
					if(userAgent != null && userAgent.Length >0)
					{
						// If they aren't FeedBurner and they aren't asking for a category, redirect them!
						if (userAgent.StartsWith("FeedBurner") == false && (category == null || category.Length == 0))
						{
							current.Response.StatusCode = 301;
							current.Response.Status = "301 Moved Permanently";
							current.Response.RedirectLocation = Utils.GetRssUrl(siteConfig);
							return true;
						}
					}
				}
			}
			return false;				
		}

		private SyndicationFeed GetFeedCore(string category, int maxDayCount, int maxEntryCount)
		{
            if (RedirectToFeedBurnerIfNeeded(category) == true)
			{
				return null;
			}
            
            
			EntryCollection entries = null;
			//We only build the entries if blogcore doesn't exist and we'll need them later...
			if (dataService.GetLastEntryUpdate() == DateTime.MinValue)
			{
				entries = BuildEntries(category, maxDayCount, maxEntryCount);
			}
			
			//Try to get out as soon as possible with as little CPU as possible
			if ( inASMX )
			{
				DateTime lastModified = Utils.GetLatestModifedEntryDateTime(dataService, entries);
				if (Utils.GetStatusNotModified(lastModified))
					return null;
			}

			if ( inASMX )
			{
				string referrer = WebOperationContext.Current.IncomingRequest.Headers[HttpRequestHeader.Referer];
				if (ReferralBlackList.IsBlockedReferrer(referrer))
				{
					if (siteConfig.EnableReferralUrlBlackList404s)
					{
						return null;
					}
				}
				else
				{
					loggingService.AddReferral(
						new LogDataItem(
						WebOperationContext.Current.IncomingRequest.UriTemplateMatch.RequestUri.ToString(),
						referrer,
						WebOperationContext.Current.IncomingRequest.Headers[HttpRequestHeader.UserAgent],
						HttpContext.Current.Request.UserHostName));
				}
			}

			//not-modified didn't work, do we have this in cache?
			string CacheKey = "Rss:" + category + ":" + maxDayCount.ToString() + ":" + maxEntryCount.ToString();

			SyndicationFeed feed = System.Web.Hosting.HostingEnvironment.Cache[CacheKey] as SyndicationFeed;
			if (feed == null) //we'll have to build it...
			{
				//However, if we made it this far, the not-modified check didn't work, and we may not have entries...
				if(entries == null)
				{
					entries = BuildEntries(category, maxDayCount, maxEntryCount);			
				}

                DateTime lastBuildDate = DateTime.Today;
                List<SyndicationItem> items = new List<SyndicationItem>();
                foreach (Entry entry in entries)
                {
                    if (entry.IsPublic == false || entry.Syndicated == false)
                    {
                        continue;
                    }
                    SyndicationItem item = new SyndicationItem();
                    item.Title = SyndicationContent.CreatePlaintextContent(entry.Title);
                    item.Id = Utils.GetPermaLinkUrl(siteConfig, entry.EntryId);
                    item.Links.Add(SyndicationLink.CreateAlternateLink(new Uri(Utils.GetPermaLinkUrl(siteConfig, entry.EntryId))));

                    MembershipUser user = Membership.GetUser(entry.Author);
                    //Scott Hanselman: According to the RSS 2.0 spec and FeedValidator.org, 
                    // we can have EITHER an Author tag OR the preferred dc:creator tag, but NOT BOTH.
                    //					if (user != null && user.EmailAddress != null && user.EmailAddress.Length > 0)
                    //					{
                    //						if (user.DisplayName != null && user.DisplayName.Length > 0)
                    //						{
                    //							item.Author = String.Format("{0} ({1})", user.EmailAddress, user.DisplayName);
                    //						}
                    //						else 
                    //						{
                    //							item.Author = user.EmailAddress;
                    //						}
                    //					}
                    XmlDocument doc2 = new XmlDocument();
                    XmlElement trackbackPing = doc2.CreateElement("trackback", "ping", "http://madskills.com/public/xml/rss/module/trackback/");
                    trackbackPing.InnerText = Utils.GetTrackbackUrl(siteConfig, entry.EntryId);
                    item.ElementExtensions.Add(trackbackPing);
                    

                    XmlElement pingbackServer = doc2.CreateElement("pingback", "server", "http://madskills.com/public/xml/rss/module/pingback/");
                    pingbackServer.InnerText = new Uri(new Uri(Utils.GetBaseUrl(siteConfig)), "pingback.svc").ToString();
                    item.ElementExtensions.Add(pingbackServer);

                    XmlElement pingbackTarget = doc2.CreateElement("pingback", "target", "http://madskills.com/public/xml/rss/module/pingback/");
                    pingbackTarget.InnerText = Utils.GetPermaLinkUrl(siteConfig, entry.EntryId);
                    item.ElementExtensions.Add(pingbackTarget);

                    XmlElement dcCreator = doc2.CreateElement("dc", "creator", "http://purl.org/dc/elements/1.1/");
                    if (user != null)
                    {
                        // HACK AG No author e-mail address in feed items.
                        //						if (user.DisplayName != null && user.DisplayName.Length > 0)
                        //						{
                        //							if(user.EmailAddress != null && user.EmailAddress.Length > 0)
                        //							{
                        //								dcCreator.InnerText = String.Format("{0} ({1})", user.EmailAddress, user.DisplayName);
                        //							}
                        //							else
                        //							{
                        dcCreator.InnerText = user.UserName;
                        //							}
                        //						}
                        //						else 
                        //						{
                        //							dcCreator.InnerText = user.EmailAddress;
                        //						}
                    }
                    item.ElementExtensions.Add(dcCreator);

                    if (siteConfig.EnableComments)
                    {
                        if (entry.AllowComments)
                        {
                            XmlElement commentApi = doc2.CreateElement("wfw", "comment", "http://wellformedweb.org/CommentAPI/");
                            commentApi.InnerText = Utils.GetCommentViewUrl(siteConfig, entry.EntryId);
                            item.ElementExtensions.Add(commentApi);
                        }

                        XmlElement commentRss = doc2.CreateElement("wfw", "commentRss", "http://wellformedweb.org/CommentAPI/");
                        commentRss.InnerText = Utils.GetEntryCommentsRssUrl(siteConfig, entry.EntryId);
                        item.ElementExtensions.Add(commentRss);

                        //for RSS conformance per FeedValidator.org
                        int commentsCount = dataService.GetPublicCommentsFor(entry.EntryId).Count;
                        if (commentsCount > 0)
                        {
                            XmlElement slashComments = doc2.CreateElement("slash", "comments", "http://purl.org/rss/1.0/modules/slash/");
                            slashComments.InnerText = commentsCount.ToString();
                            item.ElementExtensions.Add(slashComments);
                        }
                        //FIX: item.Comments = Utils.GetCommentViewUrl(siteConfig, entry.EntryId);
                    }
                    //FIX: item.Language = entry.Language;

                    if (entry.Categories != null && entry.Categories.Length > 0)
                    {
                        string[] cats = entry.Categories.Split(';');
                        foreach (string c in cats)
                        {
                            item.Categories.Add(new SyndicationCategory(c.Replace('|', '/')));
                        }
                    }
                    foreach( Attachment attachment in entry.Attachments)
                    {
                        // RSS currently supports only a single enclsoure so we return the first one	
                        item.Links.Add( SyndicationLink.CreateMediaEnclosureLink(
                            new Uri(Utils.GetEnclosureLinkUrl(entry.EntryId, attachment)),
                            attachment.Type,
                            attachment.Length));
                    }
                    item.PublishDate = entry.CreatedUtc;
                    lastBuildDate = entry.CreatedUtc;
                    

                    if (!siteConfig.AlwaysIncludeContentInRSS &&
                        entry.Description != null &&
                        entry.Description.Trim().Length > 0)
                    {
                        item.Content = SyndicationContent.CreateHtmlContent(PreprocessItemContent(entry.EntryId, entry.Description));

                    }
                    else
                    {
                        item.Content = SyndicationContent.CreateHtmlContent(ContentFormatter.FormatContentAsHTML(PreprocessItemContent(entry.EntryId, entry.Content)));
                        //try
                        //{
                        //    doc2.LoadXml(ContentFormatter.FormatContentAsXHTML(PreprocessItemContent(entry.EntryId, entry.Content)));
                        //    anyElements.Add((XmlElement)doc2.SelectSingleNode("//*[local-name() = 'body'][namespace-uri()='http://www.w3.org/1999/xhtml']"));
                        //}
                        //catch //(Exception ex)
                        //{
                        //    //Debug.Write(ex.ToString());
                        //    // absorb
                        //}
                    }
                    items.Add(item);
                }

				feed = new SyndicationFeed(items);
                //feed.Namespaces.Add("dc","http://purl.org/dc/elements/1.1/");
				//feed.Namespaces.Add("trackback","http://madskills.com/public/xml/rss/module/trackback/");
				//feed.Namespaces.Add("pingback","http://madskills.com/public/xml/rss/module/pingback/");
				//if (siteConfig.EnableComments) 
				//{
				//	feed.Namespaces.Add("wfw","http://wellformedweb.org/CommentAPI/");
				//	feed.Namespaces.Add("slash","http://purl.org/rss/1.0/modules/slash/");
				//}
				//RssChannel ch = new RssChannel();
				
				if(category == null)
				{
					feed.Title = new TextSyndicationContent(siteConfig.Title);
				}
				else
				{
					feed.Title = new TextSyndicationContent(siteConfig.Title + " - " + category);
				}				
				
				if ( siteConfig.Description == null || siteConfig.Description.Trim().Length == 0 )
				{
					feed.Description =  new TextSyndicationContent(siteConfig.Subtitle);
				}
				else
				{
					feed.Description= new TextSyndicationContent(siteConfig.Description);
				}
            
				feed.Links.Add(SyndicationLink.CreateAlternateLink(new Uri(Utils.GetBaseUrl(siteConfig))));
				feed.Copyright = new TextSyndicationContent(siteConfig.Copyright);
				if (siteConfig.RssLanguage != null && siteConfig.RssLanguage.Length >0)
				{
					feed.Language = siteConfig.RssLanguage;
				}
				feed.Authors.Add(new SyndicationPerson(siteConfig.Contact));
				feed.ImageUrl = null;
                
				if (siteConfig.ChannelImageUrl != null && siteConfig.ChannelImageUrl.Trim().Length > 0)
				{
					if (siteConfig.ChannelImageUrl.StartsWith("http"))
					{
						feed.ImageUrl = new Uri(siteConfig.ChannelImageUrl);
					}
					else
					{
						feed.ImageUrl = new Uri(Utils.RelativeToRoot(siteConfig,siteConfig.ChannelImageUrl));
					}
				}
				System.Web.Hosting.HostingEnvironment.Cache.Insert(CacheKey,feed,null,DateTime.Now.AddMinutes(5),System.Web.Caching.Cache.NoSlidingExpiration);
			}
			return feed;
		}

		
		private CommentCollection ReorderComments(CommentCollection _comments) 
		{
            CommentCollection tempCol = new CommentCollection( _comments );
            tempCol.Sort( new CommentSorter() );
            return tempCol;
		}

        private SyndicationFeed GetCommentsFeedCore(CommentCollection _com)
		{
			return GetCommentsFeedCore(_com,String.Empty);
		}

		private SyndicationFeed GetCommentsFeedCore(CommentCollection _com, string guid) 
		{
			//Try to get out as soon as possible with as little CPU as possible
			if ( inASMX )
			{
				DateTime lastModified = Utils.GetLatestModifedCommentDateTime(dataService, _com);
				if (Utils.GetStatusNotModified(lastModified))
					return null;
			}

			if ( inASMX )
			{
				string referrer = WebOperationContext.Current.IncomingRequest.Headers[HttpRequestHeader.Referer];
				if (ReferralBlackList.IsBlockedReferrer(referrer) == false)
				{
					loggingService.AddReferral(
						new LogDataItem(
						WebOperationContext.Current.IncomingRequest.UriTemplateMatch.RequestUri.ToString(),
						referrer,
						WebOperationContext.Current.IncomingRequest.Headers[HttpRequestHeader.UserAgent],
						HttpContext.Current.Request.UserHostName));
				}            
			}
			
			
			int i = 0;
            List<SyndicationItem> items = new List<SyndicationItem>();
			foreach (Comment c in _com) 
			{
                SyndicationItem item = new SyndicationItem();
				if (i == siteConfig.RssEntryCount) { break; }
				i++;
				string tempTitle = "";
				
				if(c.TargetTitle != null && c.TargetTitle.Length > 0 ) 
				{
					tempTitle = " on \"" + c.TargetTitle + "\"";
				}

				if(c.Author == null || c.Author == "") 
				{
					item.Title = new TextSyndicationContent( "Comment" + tempTitle );
				}
				else 
				{
					item.Title = new TextSyndicationContent("Comment by " + c.Author + tempTitle);
				}

				//Per the RSS Comments Spec it makes more sense for guid and link to be the same.
				// http://blogs.law.harvard.edu/tech/rss#comments
				// 11/11/05 - SDH - Now, I'm thinking not so much...
                item.Id = Utils.GetCommentViewUrl(siteConfig, c.TargetEntryId, c.EntryId);
                item.Links.Add(SyndicationLink.CreateAlternateLink(new Uri(Utils.GetCommentViewUrl(siteConfig, c.TargetEntryId, c.EntryId))));
				
				item.PublishDate = c.CreatedUtc;
				
				item.Content = SyndicationContent.CreateHtmlContent(c.Content.Replace(Environment.NewLine, "<br />"));
				if (c.AuthorHomepage == null || c.AuthorHomepage == "") 
				{
					if (c.AuthorEmail == null || c.AuthorEmail == "") 
					{
						if(!(c.Author == null || c.Author == "") )
						{
							item.Content = SyndicationContent.CreateHtmlContent(c.Content.Replace(Environment.NewLine, "<br />") + "<br /><br />" + "Posted by: " + c.Author);
						}
					}
					else 
					{
						string content = c.Content.Replace(Environment.NewLine, "<br />");
						if (!siteConfig.SupressEmailAddressDisplay) 
						{
							item.Content = SyndicationContent.CreateHtmlContent(content + "<br /><br />" + "Posted by: " + "<a href=\"mailto:" + Utils.SpamBlocker(c.AuthorEmail) + "\">" + c.Author + "</a>");
						}
						else 
						{
							item.Content = SyndicationContent.CreateHtmlContent(content + "<br /><br />" + "Posted by: " + c.Author);
						}
							
					}
				}
				else 
				{
					if (c.AuthorHomepage.IndexOf("http://") < 0) 
					{
						c.AuthorHomepage = "http://" + c.AuthorHomepage; 
					}
					item.Content = SyndicationContent.CreateHtmlContent(c.Content.Replace(Environment.NewLine, "<br />")+"<br /><br />" + "Posted by: " + "<a href=\"" + c.AuthorHomepage +
						"\">" + c.Author + "</a>");
				}

				if (c.Author != null && c.Author.Length > 0 ) 
				{

					 	// the rss spec requires an email address in the author tag
					// and it can not be obfuscated
						// according to the feedvalidator
					string email;

					if ( !siteConfig.SupressEmailAddressDisplay ) 
					{
						email = (c.AuthorEmail != null && c.AuthorEmail.Length > 0 ? c.AuthorEmail : "unknown@unknown.org");
					} 
					else 
					{
						email = "suppressed@unknown.org";
					}

					item.Authors.Add(new SyndicationPerson(email, c.Author, c.AuthorHomepage));
				}

				//FIX: item.Comments = Utils.GetCommentViewUrl(siteConfig,c.TargetEntryId,c.EntryId);
				
				//ch.LastBuildDate = item.PubDate;
				items.Add(item);
			}


            // TODO: Figure out why this code is copied and pasted from above rather than using a function (shame!)

            SyndicationFeed feed = new SyndicationFeed(items);
            if (guid != null && guid != String.Empty)
            {
                //Set the title for this RSS Comments feed
                feed.Title = new TextSyndicationContent(siteConfig.Title + " - Comments on " + dataService.GetEntry(guid).Title);
            }
            else
            {
                feed.Title = new TextSyndicationContent(siteConfig.Title + " - Comments");
            }
            feed.Links.Add(SyndicationLink.CreateAlternateLink(new Uri(Utils.GetBaseUrl(siteConfig))));
            feed.Copyright = new TextSyndicationContent(siteConfig.Copyright);
            feed.Authors.Add(new SyndicationPerson(siteConfig.Contact));
            

			return feed;
		}

        //[OperationContract, WebGet(UriTemplate = "/GetReferrerLog?date={dateString}")]
		public LogDataItemCollection GetReferrerLog(string dateString)
		{
            DateTime date = DateTime.Parse(dateString);
			return loggingService.GetReferralsForDay(date);
		}

        //[OperationContract, WebGet(UriTemplate = "/GetCategoryList")]
		public string[] GetCategoryList()
		{
			CategoryCacheEntryCollection categories = dataService.GetCategories();
			string[] cats = new string[categories.Count];
			for (int i=0; i<cats.Length; i++)
			{
				cats[i] = categories[i].Name;
			}

			return cats;
		}

        //[OperationContract, WebGet(UriTemplate = "/GetCategoryEntries?category={categoryName}")]
		public EntryCollection GetCategoryEntries(string categoryName)
		{
			return dataService.GetEntriesForCategory( categoryName, null );
		}

        //[OperationContract, WebGet(UriTemplate = "/GetDaysWithEntries")]
		public DateTime[] GetDaysWithEntries()
		{
			return dataService.GetDaysWithEntries(new newtelligence.DasBlog.Util.UTCTimeZone());
		}

        //[OperationContract, WebGet(UriTemplate = "/GetDayEntry?date={dateString}")]
		public DayEntry GetDayEntry(string dateString)
		{
            DateTime date = DateTime.Parse(dateString);
			return dataService.GetDayEntry(date);
		}

        //[OperationContract, WebGet(UriTemplate = "/GetDayExtra?date={dateString}")]
		public DayExtra GetDayExtra(string dateString)
		{
            DateTime date = DateTime.Parse(dateString);
			return dataService.GetDayExtra(date);
		}
		
		
		
		
	}
}
